//
//  CSDatabase.m
//  CSDatabase
//
//  Created by 黄楚升 on 2021/6/14.
//

#import "CSDatabase.h"
#import <fmdb/FMDB.h>
#import <objc/runtime.h>

#define SQL_TEXT     @"TEXT" //文本
#define SQL_INTEGER  @"INTEGER" //int long integer ...
#define SQL_REAL     @"REAL" //浮点
#define SQL_BLOB     @"BLOB" //data

@interface CSDatabase ()
@property(nonatomic, strong) FMDatabase *database;
@property(nonatomic, strong) FMDatabaseQueue *dbQueue;
@end

@implementation CSDatabase

- (instancetype)initWithDatabaseName:(NSString *)databaseName
{
    self = [super init];
    if (self) {
        NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
        NSString *filePath = [path stringByAppendingPathComponent:databaseName];
        NSLog(@"数据库路径：%@",filePath);
        _database = [[FMDatabase alloc] initWithPath:filePath];
    }
    return self;
}

+ (instancetype)defaultDatabase {
    static id instance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[CSDatabase alloc] initWithDatabaseName:@"CSDatabase.db"];
    });
    return instance;
}

#pragma mark - 创建表
/// 创建表
/// @param tableName 表名
/// @param modelClass 模型类
- (BOOL)createTable:(NSString *)tableName withModelClass:(Class)modelClass {
    return [self createTable:tableName withModelClass:modelClass excludeName:nil];
}

- (BOOL)createTable:(NSString *)tableName withModelClass:(Class)modelClass excludeName:(NSArray *)nameArr
{
    if (![self.database open]) {
        return NO;
    }
    
    NSDictionary *dic = [self propertyTypeDictionaryFromClass:modelClass excludeNames:nameArr];
    
    NSArray *keys = [dic allKeys];
    
    NSMutableArray *array = [NSMutableArray arrayWithObject:@"pkid INTEGER PRIMARY KEY"];
    
    for (NSString *key in keys) {
        
        if ([key isEqualToString:@"pkid"]) {
            continue;
        }
        
        NSString *value = dic[key];
        
        [array addObject:[NSString stringWithFormat:@"%@ %@", key, value]];
    }
    
    
    NSString *sqlStr = [[NSString alloc] initWithFormat:@"CREATE TABLE IF NOT EXISTS %@ (%@)", tableName, [array componentsJoinedByString:@" , "]];

    BOOL success = [self.database executeUpdate:sqlStr];
    
    [self.database close];
    
    return success;
}

#pragma mark - 增
- (BOOL)insertIntoTable:(NSString *)tableName withParams:(NSDictionary *)params {
    
    if (![self.database open]) {
        return NO;
    }
    
    NSArray *schemes = [self schemasForTable:tableName];
    
    NSMutableArray *keys = [NSMutableArray array];
    NSMutableArray *temps = [NSMutableArray array];
    NSMutableArray *values = [NSMutableArray array];
    
    for (NSString *key in params.allKeys) {
        
        if (![schemes containsObject:key] || [key isEqualToString:@"pkid"]) {
            continue;
        }
        
        [keys addObject:key];
        [temps addObject:@"?"];
        [values addObject:params[key]];
    }
    
    NSString *keyStr = [keys componentsJoinedByString:@","];
    NSString *tempStr = [temps componentsJoinedByString:@","];

    NSString *sqlStr = [NSString stringWithFormat:@"INSERT INTO %@(%@) VALUES(%@)", tableName, keyStr, tempStr];
    
    BOOL success = [self.database executeUpdate:sqlStr withArgumentsInArray:values];
    
    [self.database close];
    
    return success;
}

/// 插入数据到表中
/// @param tableName 表名
/// @param model 数据模型
- (BOOL)insertIntoTable:(NSString *)tableName withModel:(NSObject *)model {

    NSDictionary *params = [self modelDictionaryFromModel:model];
    
    return [self insertIntoTable:tableName withParams:params];
}

#pragma mark - 删
/// 删除表中的数据
/// @param tableName 表名
/// @param whereFormat 条件语句，如果为nil，则删除所有数据
- (BOOL)deleteFromTable:(NSString *)tableName whereFormat:(NSString *)whereFormat, ... {
    
    if (![self.database open]) {
        return NO;
    }
    
    NSString *sqlStr = [NSString stringWithFormat:@"DELETE FROM %@", tableName];
    
    va_list args;
    va_start(args, whereFormat);
    NSString *whereString = whereFormat ? [[NSString alloc] initWithFormat:whereFormat locale:[NSLocale currentLocale] arguments:args] : nil;
    va_end(args);
    
    if (whereString && whereString.length > 0) {
        sqlStr = [sqlStr stringByAppendingFormat:@" %@", whereString];
    }
    
    BOOL success = [self.database executeUpdate:sqlStr];
    
    [self.database close];
    
    return success;
}

#pragma mark - 改
- (BOOL)updateFromTable:(NSString *)tableName withParams:(NSDictionary *)params whereFormat:(NSString *)whereFormat, ... {
    
    if (![self.database open]) {
        return NO;
    }
    
    NSArray *schemes = [self schemasForTable:tableName];
    
    NSMutableArray *updateKeys = [NSMutableArray array];
    
    NSMutableArray *arguments = [NSMutableArray array];
    
    for (NSString *key in params) {
        
        if (![schemes containsObject:key] || [key isEqualToString:@"pkid"]) {
            continue;
        }
        
        NSString *value = params[key];
        
        [updateKeys addObject:[NSString stringWithFormat:@"%@ = ?", key]];
        
        [arguments addObject:value];
    }
    
    NSString *sqlStr = [NSString stringWithFormat:@"UPDATE %@ SET %@", tableName, [updateKeys componentsJoinedByString:@", "]];
    
    va_list args;
    va_start(args, whereFormat);
    NSString *whereString = whereFormat ? [[NSString alloc] initWithFormat:whereFormat locale:[NSLocale currentLocale] arguments:args] : nil;
    va_end(args);
    
    if (whereString && whereString.length > 0) {
        
        sqlStr = [sqlStr stringByAppendingFormat:@" %@", whereString];
    }
    
    BOOL success = [self.database executeUpdate:sqlStr withArgumentsInArray:arguments];
    
    [self.database close];
    
    return success;
}

/// 更新表
/// @param tableName 表名
/// @param model 数据模型
/// @param whereFormat  条件语句，如果为nil，则删除所有数据
- (BOOL)updateFromTable:(NSString *)tableName withModel:(NSObject *)model whereFormat:(NSString *)whereFormat, ... {
    
    NSDictionary *params = [self modelDictionaryFromModel:model];
    
    return [self updateFromTable:tableName withParams:params whereFormat:whereFormat];
}

#pragma mark - 查
/// 查询表中数据
/// @param tableName 表名
/// @param modelClass 返回数据所属类
/// @param whereFormat 条件语句拼接
- (nullable NSArray *)queryFromTable:(NSString *)tableName withModelClass:(Class)modelClass whereFormat:(NSString *)whereFormat, ... {
    
    if (![self.database open]) {
        return nil;
    }
    
    NSString *sqlStr = [NSString stringWithFormat:@"SELECT * FROM %@", tableName];
    
    va_list args;
    va_start(args, whereFormat);
    NSString *whereString = whereFormat ? [[NSString alloc] initWithFormat:whereFormat locale:[NSLocale currentLocale] arguments:args] : nil;
    va_end(args);
    
    if (whereString && whereString.length > 0) {
        
        sqlStr = [sqlStr stringByAppendingFormat:@" %@", whereString];
    }
    
    FMResultSet *resultSet = [self.database executeQuery:sqlStr];
    
    NSArray *schemes = [self schemasForTable:tableName];
    
    NSMutableArray *results = [NSMutableArray array];
    
    NSDictionary *propertyTypeDic = [self propertyTypeDictionaryFromClass:modelClass excludeNames:nil];
    
    while ([resultSet next]) {
        
        id model = [modelClass new];
        
        for (NSString *name in schemes) {
            if ([propertyTypeDic[name] isEqualToString:SQL_TEXT]) {
                
                id value = [resultSet stringForColumn:name];
                if (value) {
                    [model setValue:value forKey:name];
                }
                
            } else if ([propertyTypeDic[name] isEqualToString:SQL_INTEGER]) {
                
                [model setValue:@([resultSet longLongIntForColumn:name]) forKey:name];
                
            } else if ([propertyTypeDic[name] isEqualToString:SQL_REAL]) {
                
                [model setValue:@([resultSet doubleForColumn:name]) forKey:name];
                
            } else if ([propertyTypeDic[name] isEqualToString:SQL_BLOB]) {
                
                id value = [resultSet dataForColumn:name];
                if (value) {
                    [model setValue:value forKey:name];
                }
            }
        }
        
        [results addObject:model];
    }
    
    [self.database close];
    
    return results;
}

#pragma mark - Private

- (NSArray *)schemasForTable:(NSString *)tableName {
    
    NSMutableArray *array = [NSMutableArray array];
    
    FMResultSet *resultSet = [self.database getTableSchema:tableName];
    
    while ([resultSet next]) {
        [array addObject:[resultSet stringForColumn:@"name"]];
    }
    
    return array;
}

/// 获取模型的属性名、属性值 字典
/// @param model <#model description#>
- (NSDictionary *)modelDictionaryFromModel:(NSObject *)model
{
    NSMutableDictionary *mDic = [NSMutableDictionary dictionaryWithCapacity:0];
    unsigned int outCount;
    objc_property_t *properties = class_copyPropertyList([model class], &outCount);
    
    for (int i = 0; i < outCount; i++) {
        
        NSString *name = [NSString stringWithCString:property_getName(properties[i]) encoding:NSUTF8StringEncoding];
        
        id value = [model valueForKey:name];
        if (value) {
            [mDic setObject:value forKey:name];
        }
    }
    free(properties);
    
    return mDic;
}

- (NSString *)propertTypeConvert:(NSString *)typeStr
{
    NSString *resultStr = nil;
    if ([typeStr hasPrefix:@"T@\"NSString\""]) {
        resultStr = SQL_TEXT;
    } else if ([typeStr hasPrefix:@"T@\"NSData\""]) {
        resultStr = SQL_BLOB;
    } else if ([typeStr hasPrefix:@"Ti"]||[typeStr hasPrefix:@"TI"]||[typeStr hasPrefix:@"Ts"]||[typeStr hasPrefix:@"TS"]||[typeStr hasPrefix:@"T@\"NSNumber\""]||[typeStr hasPrefix:@"TB"]||[typeStr hasPrefix:@"Tq"]||[typeStr hasPrefix:@"TQ"]) {
        resultStr = SQL_INTEGER;
    } else if ([typeStr hasPrefix:@"Tf"] || [typeStr hasPrefix:@"Td"]){
        resultStr= SQL_REAL;
    }
    
    return resultStr;
}

/// 获取模型的属性名、属性类型 字典
/// @param modelClass 模型类
/// @param excludeNames 排除项
- (NSDictionary *)propertyTypeDictionaryFromClass:(Class)modelClass excludeNames:(nullable NSArray *)excludeNames
{
    NSMutableDictionary *mDic = [NSMutableDictionary dictionaryWithCapacity:0];
    unsigned int outCount;
    objc_property_t *properties = class_copyPropertyList(modelClass, &outCount);
    for (int i = 0; i < outCount; i++) {
        
        NSString *name = [NSString stringWithCString:property_getName(properties[i]) encoding:NSUTF8StringEncoding];
        if (excludeNames && ![excludeNames containsObject:name]) {
            continue;
        }
        
        NSString *type = [NSString stringWithCString:property_getAttributes(properties[i]) encoding:NSUTF8StringEncoding];
        
        id value = [self propertTypeConvert:type];
        if (value) {
            [mDic setObject:value forKey:name];
        }
        
    }
    free(properties);
    
    return mDic;
}


@end
